/**
 * component-sha1
 */

/**
 * Configurable variables
 */

const hexcase = 0 /* hex output format. 0 - lowercase; 1 - uppercase        */
// const b64pad = '=' /* base-64 pad character. "=" for strict RFC compliance   */
const chrsz = 8 /* bits per input character. 8 - ASCII; 16 - Unicode      */

/**
 * Perform a simple self-test to see if the VM is working
 */

// const sha1VmTest = function () {
//   return hex_sha1('abc') == 'a9993e364706816aba3e25717850c26c9cd0d89d'
// }

/**
 * Add integers, wrapping at 2^32.
 * This uses 16-bit operations internally to work around bugs in some JS interpreters.
 */
const safeAdd = function (x, y) {
  const lsw = (x & 0xFFFF) + (y & 0xFFFF)
  const msw = (x >> 16) + (y >> 16) + (lsw >> 16)

  return (msw << 16) | (lsw & 0xFFFF)
}
/**
 * Bitwise rotate a 32-bit number to the left.
 */
const rol = function (num, cnt) {
  return (num << cnt) | (num >>> (32 - cnt))
}
/**
 * Calculate the SHA-1 of an array of big-endian words, and a bit length
 */

const coreSha1 = function (x, len) {
  /* append padding */
  x[len >> 5] |= 0x80 << (24 - len % 32)
  x[((len + 64 >> 9) << 4) + 15] = len

  const w = Array(80)
  let a = 1732584193
  let b = -271733879
  let c = -1732584194
  let d = 271733878
  let e = -1009589776

  let i
  let j

  for (i = 0; i < x.length; i += 16) {
    const olda = a
    const oldb = b
    const oldc = c
    const oldd = d
    const olde = e

    for (j = 0; j < 80; j += 1) {
      if (j < 16) {
        w[j] = x[i + j]
      } else {
        w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1)
      }

      const t = safeAdd(safeAdd(rol(a, 5), sha1Ft(j, b, c, d)), safeAdd(safeAdd(e, w[j]), sha1Kt(j)))
      e = d
      d = c
      c = rol(b, 30)
      b = a
      a = t
    }

    a = safeAdd(a, olda)
    b = safeAdd(b, oldb)
    c = safeAdd(c, oldc)
    d = safeAdd(d, oldd)
    e = safeAdd(e, olde)
  }

  return [a, b, c, d, e]
}

/**
 * Perform the appropriate triplet combination function
 * for the current iteration
 */

const sha1Ft = function (t, b, c, d) {
  if (t < 20) return (b & c) | ((~b) & d)
  if (t < 40) return b ^ c ^ d
  if (t < 60) return (b & c) | (b & d) | (c & d)

  return b ^ c ^ d
}

/**
 * Determine the appropriate additive constant for the current iteration
 */

const sha1Kt = function (t) {
  return (t < 20)
    ? 1518500249
    : (t < 40)
      ? 1859775393
      : (t < 60) ? -1894007588 : -899497514
}

/**
 * Convert an 8-bit or 16-bit string to an array of big-endian words
 * In 8-bit function, characters >255 have their hi-byte silently ignored.
 */
const str2binb = function (str) {
  const bin = []
  const mask = (1 << chrsz) - 1
  let i

  for (i = 0; i < str.length * chrsz; i += chrsz) {
    bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i % 32)
  }

  return bin
}
/**
 * Calculate the HMAC-SHA1 of a key and some data
 */

const coreHmacSha1 = function (key, data) {
  let bkey = str2binb(key)
  if (bkey.length > 16) {
    bkey = coreSha1(bkey, key.length * chrsz)
  }
  const ipad = Array(16)
  const opad = Array(16)
  let i

  for (i = 0; i < 16; i += 1) {
    ipad[i] = bkey[i] ^ 0x36363636
    opad[i] = bkey[i] ^ 0x5C5C5C5C
  }

  const hash = coreSha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz)

  return coreSha1(opad.concat(hash), 512 + 160)
}

// /**
//  * Convert an array of big-endian words to a string
//  */
//
// const binb2str = function (bin) {
//   let str = ''
//   const mask = (1 << chrsz) - 1
//   let i
//
//   for (i = 0; i < bin.length * 32; i += chrsz) {
//     str += String.fromCharCode((bin[i >> 5] >>> (32 - chrsz - i % 32)) & mask)
//   }
//
//   return str
// }

/*
 * Convert an array of big-endian words to a hex string.
 */
const binb2hex = function (binarray) {
  const hexTab = hexcase ? '0123456789ABCDEF' : '0123456789abcdef'
  let str = ''
  let i

  for (i = 0; i < binarray.length * 4; i += 1) {
    str += hexTab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4)) & 0xF) +
      hexTab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8)) & 0xF)
  }

  return str
}

// /**
//  * Convert an array of big-endian words to a base-64 string
//  */
//
// const binb2b64 = function (binarray) {
//   const tab = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
//   let str = ''
//   let i
//   let j
//
//   for (i = 0; i < binarray.length * 4; i += 3) {
//     const triplet = (((binarray[i >> 2] >> 8 * (3 - i % 4)) & 0xFF) << 16) |
//       (((binarray[i + 1 >> 2] >> 8 * (3 - (i + 1) % 4)) & 0xFF) << 8) |
//       ((binarray[i + 2 >> 2] >> 8 * (3 - (i + 2) % 4)) & 0xFF)
//     for (j = 0; j < 4; j += 1) {
//       if (i * 8 + j * 6 > binarray.length * 32) {
//         str += b64pad
//       } else {
//         str += tab.charAt((triplet >> 6 * (3 - j)) & 0x3F)
//       }
//     }
//   }
//
//   return str
// }

// /**
//  * hex
//  * Hashes a string using SHA1 and returns a hex representation of it.
//  *
//  * @param {String} s
//  * @returns {String}
//  * @api public
//  */
//
// function hex (s) {
//   return binb2hex(coreSha1(str2binb(s), s.length * chrsz))
// }
//
// /**
//  * base64
//  * Hashes a string using SHA1 and returns a base64 representation of it.
//  *
//  * @param {String} s
//  * @returns {String}
//  * @api public
//  */
// function base64 (s) {
//   return binb2b64(coreSha1(str2binb(s), s.length * chrsz))
// }
//
// /**
//  * str
//  * Hashes a string using SHA1 and returns a string representation of it.
//  *
//  * @param {String} s
//  * @returns {String}
//  * @api public
//  */
// function str (s) {
//   return binb2str(coreSha1(str2binb(s), s.length * chrsz))
// }
//
//
// /**
//  * base64_hmac
//  * Calculates the HMAC-SHA1 of a key and some data,
//  * returning a base64 representation of it.
//  *
//  * @param {String} key
//  * @param {String} data
//  * @returns {String}
//  * @api public
//  */
// function base64_hmac (key, data) {
//   return binb2b64(coreHmacSha1(key, data))
// }
//
// /**
//  * str_hmac
//  * Calculates the HMAC-SHA1 of a key and some data,
//  * returning a string representation of it.
//  *
//  * @param {String} key
//  * @param {String} data
//  * @returns {String}
//  * @api public
//  */
// function str_hmac (key, data) {
//   return binb2str(coreHmacSha1(key, data))
// }

/**
 * hexHmac
 * Calculates the HMAC-SHA1 of a key and some data,
 * returning a hex representation of it.
 *
 * @param {String} key
 * @param {String} data
 * @returns {String}
 * @api public
 */
function hexHmac (key, data) {
  return binb2hex(coreHmacSha1(key, data))
}

export default hexHmac
